﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace HeliGame
{
    public class Walls
    {        
        public Texture texture;
        VertexBuffer vb;
        IndexBuffer ib;
        float ceilingHeight;
        const float xTextureScale = 0.08f;
        const float yTextureScale = 0.08f;
        const float houseScale = 2;
        int numOfVertices, numOfIndices;

        /*Vector2[] housePlan = {
                new Vector2(0, 0),
                new Vector2(0, 7),
                new Vector2(5, 7),
                new Vector2(5, 6),
                new Vector2(5.2f, 6),
                new Vector2(5.2f, 9),
                new Vector2(10, 9),
                new Vector2(10, 4),
                new Vector2(5.2f, 4),
                new Vector2(5.2f, 5),
                new Vector2(5, 5),
                new Vector2(5, 0),
                new Vector2(0, 0)                                                                   
            };*/

        Vector2[] housePlan = {
                new Vector2(0, 0),
                new Vector2(0, 3),
                new Vector2(-6, 3),
                new Vector2(-6, 13),
                new Vector2(-5, 15),
                new Vector2(-3, 16),
                new Vector2(-1, 16),
                new Vector2(1, 15),
                new Vector2(2, 13),
                new Vector2(2, 11),
                new Vector2(1, 9),
                new Vector2(1, 7),
                new Vector2(0, 5),    
                
                new Vector2(2, 5),
                new Vector2(2, 7),
                new Vector2(3, 7),
                new Vector2(3, 8),
                new Vector2(6, 8),
                new Vector2(6, 7),
                new Vector2(8, 7),
                new Vector2(8, 9),
                new Vector2(14, 9),
                new Vector2(14, 4),
                new Vector2(8, 4),
                new Vector2(8, 5),
                new Vector2(5, 5),                               

                new Vector2(5, 2),
                new Vector2(5.2f, 2),
                new Vector2(5.2f, 3),
                new Vector2(9, 3),
                new Vector2(9, -5),
                new Vector2(8, -5),
                new Vector2(7, -4),
                new Vector2(4, -4),
                new Vector2(3, -3),
                new Vector2(1, -3),
                new Vector2(1, -2),
                new Vector2(2, -2),
                new Vector2(2, -1), 

                new Vector2(5.2f, -1),
                new Vector2(5.2f, 1),
                new Vector2(5, 1),
                new Vector2(5, 0),
                new Vector2(0, 0)
            };

        Vector2[] cameraCollisionLayout;

        private void BuildCameraCollisionLayout()
        {
            const float distance = 0.2f;
            cameraCollisionLayout = new Vector2[housePlan.Length];
            int i;
            Vector2 line, shiftedLineStart, shiftedLineEnd, previousShiftedLineStart, previousShiftedLineEnd,
                firstShiftedLineStart, firstShiftedLineEnd;

            line = housePlan[1] - housePlan[0];
            line = new Vector2(line.Y, -line.X); //obróć o 90 st. w prawo
            line.Normalize();
            line = line * distance;
            firstShiftedLineStart = shiftedLineStart = housePlan[0] + line;
            firstShiftedLineEnd = shiftedLineEnd = housePlan[1] + line;

            Vector2 intersection;
            for (i = 1; i < housePlan.Length - 1; i++)
            {
                previousShiftedLineStart = shiftedLineStart;
                previousShiftedLineEnd = shiftedLineEnd;

                line = housePlan[i + 1] - housePlan[i];
                line = new Vector2(line.Y, -line.X);
                line.Normalize();
                line = line * distance;
                shiftedLineStart = housePlan[i + 1] + line;
                shiftedLineEnd = housePlan[i] + line;

                FindExtrapolatedLineIntersection(previousShiftedLineStart, previousShiftedLineEnd, shiftedLineStart,
                    shiftedLineEnd, out intersection);
                cameraCollisionLayout[i] = intersection;
            }
            FindExtrapolatedLineIntersection(firstShiftedLineStart, firstShiftedLineEnd, shiftedLineStart,
                    shiftedLineEnd, out intersection);
            cameraCollisionLayout[0] = cameraCollisionLayout[housePlan.Length - 1] = intersection;
        }

        private void FindExtrapolatedLineIntersection(Vector2 start1, Vector2 end1, Vector2 start2, Vector2 end2, out Vector2 intersection)
        {
            float denom = ((end1.X - start1.X) * (end2.Y - start2.Y)) - ((end1.Y - start1.Y) * (end2.X - start2.X));

            //  AB & CD równoległe 
            intersection = Vector2.Zero;
            if (denom == 0) throw new Exception("Równoległe odcinki - brak przecięcia");

            float numer = ((start1.Y - start2.Y) * (end2.X - start2.X)) - ((start1.X - start2.X) * (end2.Y - start2.Y));
            float r = numer / denom;
            float numer2 = ((start1.Y - start2.Y) * (end1.X - start1.X)) - ((start1.X - start2.X) * (end1.Y - start1.Y));
            float s = numer2 / denom;
            //if ((r < 0 || r > 1) || (s < 0 || s > 1)) return false; //zakomentowane, by szukać także poza końcami odcinków

            // znajdź punkt przecięcia
            intersection = new Vector2();
            intersection.X = start1.X + (r * (end1.X - start1.X));
            intersection.Y = start1.Y + (r * (end1.Y - start1.Y));
        }

        private bool FindLineIntersection(Vector2 start1, Vector2 end1, Vector2 start2, Vector2 end2, out Vector2 intersection)
        {
            float denom = ((end1.X - start1.X) * (end2.Y - start2.Y)) - ((end1.Y - start1.Y) * (end2.X - start2.X));

            //  AB & CD równoległe 
            intersection = Vector2.Zero;
            if (denom == 0) return false;

            float numer = ((start1.Y - start2.Y) * (end2.X - start2.X)) - ((start1.X - start2.X) * (end2.Y - start2.Y));
            float r = numer / denom;
            float numer2 = ((start1.Y - start2.Y) * (end1.X - start1.X)) - ((start1.X - start2.X) * (end1.Y - start1.Y));
            float s = numer2 / denom;
            if ((r < 0 || r > 1) || (s < 0 || s > 1)) return false;

            // znajdź punkt przecięcia
            intersection = new Vector2();
            intersection.X = start1.X + (r * (end1.X - start1.X));
            intersection.Y = start1.Y + (r * (end1.Y - start1.Y));

            return true;
        }

        public bool CheckColissionWithCamera(Vector3 camPosition, Vector3 camLookAt, out Vector3 collisionPoint)
        {
            Vector2 l1 = new Vector2(camPosition.X, camPosition.Z);
            Vector2 l2 = new Vector2(camLookAt.X, camLookAt.Z);
            Vector3 tempCP = Vector3.Zero;
            Vector2 collisionPoint2D;
            collisionPoint = Vector3.Zero;

            float minDistance = 10000;
            bool found = false;

            for (int i = 0; i < cameraCollisionLayout.Length - 1; i++)
            {
                if (FindLineIntersection(l1, l2, cameraCollisionLayout[i], cameraCollisionLayout[i + 1], 
                    out collisionPoint2D))
                {
                    tempCP = new Vector3(collisionPoint2D.X, camPosition.Y, collisionPoint2D.Y);
                    if (Vector3.Distance(camLookAt, tempCP) < minDistance)
                    {
                        minDistance = Vector3.Distance(camLookAt, tempCP);
                        collisionPoint = tempCP;
                        found = true;
                    }
                }
            }
            return found;
        }

        public bool CheckCollisionWithHeli(RotatedBBox heliBox, out Vector2 colissionPoint)
        {
            Vector2 l1 = new Vector2(heliBox.v1.X, heliBox.v1.Z);
            Vector2 l2 = new Vector2(heliBox.v2.X, heliBox.v2.Z);
            Vector2 l3 = new Vector2(heliBox.v3.X, heliBox.v3.Z);
            Vector2 l4 = new Vector2(heliBox.v4.X, heliBox.v4.Z);
            colissionPoint = Vector2.Zero;
            for (int i = 0; i < housePlan.Length - 1; i++)
            {
                if (FindLineIntersection(l1, l2, housePlan[i], housePlan[i + 1], out colissionPoint)) return true;
                if (FindLineIntersection(l2, l3, housePlan[i], housePlan[i + 1], out colissionPoint)) return true;
                if (FindLineIntersection(l3, l4, housePlan[i], housePlan[i + 1], out colissionPoint)) return true;
                if (FindLineIntersection(l4, l1, housePlan[i], housePlan[i + 1], out colissionPoint)) return true;
            }
            return false;
        }

        public Walls(GraphicsDevice device, Texture texture, float ceilingHeight)
        {
            BuildCameraCollisionLayout();

            this.texture = texture;
            this.ceilingHeight = ceilingHeight;

            for (int i = 0; i < housePlan.Length; i++) 
            {
                housePlan[i].X *= houseScale;
                housePlan[i].Y *= -houseScale;

                cameraCollisionLayout[i].X *= houseScale;
                cameraCollisionLayout[i].Y *= -houseScale;
            }

            int numOfWalls = (housePlan.Length - 1);

            numOfVertices = 4 * numOfWalls;
            numOfIndices = 6 * numOfWalls;

            vb = new VertexBuffer(device, WallVertex.VD, numOfVertices, BufferUsage.WriteOnly);
            ib = new IndexBuffer(device, IndexElementSize.SixteenBits, numOfIndices, BufferUsage.WriteOnly);

            List<WallVertex> wallVertices = new List<WallVertex>();
            List<short> wallIndices = new List<short>();

            for (int i = 0; i < housePlan.Length - 1; i++)
            {
                Vector2 wallVec = new Vector2(housePlan[i+1].X - housePlan[i].X, housePlan[i+1].Y - housePlan[i].Y);
                Vector2 wallNormal = new Vector2(-wallVec.Y, wallVec.X);
                wallNormal.Normalize();
                Vector3 wallNormal3D = new Vector3(wallNormal.X, 0, wallNormal.Y);
                float wallLength = wallVec.Length();

                wallVertices.Add(new WallVertex(
                    new Vector3(housePlan[i].X, 0, housePlan[i].Y),
                    wallNormal3D,
                    Color.Green,
                    new Vector2(0, 0)));

                wallVertices.Add(new WallVertex(
                    new Vector3(housePlan[i + 1].X, 0, housePlan[i + 1].Y),
                    wallNormal3D,
                    Color.Blue,
                    new Vector2(wallLength * xTextureScale, 0)));

                wallVertices.Add(new WallVertex(
                    new Vector3(housePlan[i].X, ceilingHeight, housePlan[i].Y),
                    wallNormal3D,
                    Color.Red,
                    new Vector2(0, ceilingHeight * yTextureScale)));
                              

                wallVertices.Add(new WallVertex(
                    new Vector3(housePlan[i+1].X, ceilingHeight, housePlan[i+1].Y),
                    wallNormal3D,
                    Color.Yellow,
                    new Vector2(wallLength * xTextureScale, ceilingHeight * yTextureScale)));


                wallIndices.Add((short)(0 + 4 * i));
                wallIndices.Add((short)(2 + 4 * i));
                wallIndices.Add((short)(1 + 4 * i));

                wallIndices.Add((short)(1 + 4 * i));
                wallIndices.Add((short)(2 + 4 * i));
                wallIndices.Add((short)(3 + 4 * i));
            }

            vb.SetData<WallVertex>(wallVertices.ToArray());
            ib.SetData<short>(wallIndices.ToArray());
        }

        public void Draw(Effect effect, GraphicsDevice device)
        {
            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Apply();
                device.Indices = ib;
                device.SetVertexBuffer(vb);                
                device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, numOfVertices, 0, numOfVertices / 2);
                
                
                //device.DrawPrimitives(PrimitiveType.TriangleList, 0, 1);
                 
                /*device.DrawUserPrimitives<WallVertex>(PrimitiveType.TriangleList,
                    new WallVertex[] {
                        new WallVertex(new Vector3(-1, -1, 0), new Vector3(0,0,0), Color.Blue, new Vector2(0, 0)),
                        new WallVertex(new Vector3(-1, 1, 0), new Vector3(0,0,0), Color.Green, new Vector2(0, 0)),
                        new WallVertex(new Vector3(1, -1, 0), new Vector3(0,0,0), Color.Red, new Vector2(0, 0)),
                        new WallVertex(new Vector3(1, -1, 0), new Vector3(0,0,0), Color.Red, new Vector2(0, 0)),
                        new WallVertex(new Vector3(-1, 1, 0), new Vector3(0,0,0), Color.Green, new Vector2(0, 0)),
                        new WallVertex(new Vector3(1, 1, 0), new Vector3(0,0,0), Color.Yellow, new Vector2(0, 0))
                    }, 0, 2, WallVertex.VD);
                */
                
            }
        }

    }
}
